/* str.c : dgnsc string func
 * Copyright (C) 2009-2011 drangon <drangon.zhou@gmail.com>
 * 2011-09
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <dgnsc/str.h>
#include <dgnsc/common.h>

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

struct dgn_str_st
{
	int cap;  // special, cap == 0, str not malloc, point to len ( must be 0 )
	int len;
	char * str;
};

dgn_str_t dgn_str_init( int capacity )
{
	struct dgn_str_st * s = (struct dgn_str_st *)malloc( sizeof(struct dgn_str_st) );
	s->len = 0;
	if( capacity > 0 ) {
		s->cap = capacity;
		s->str = (char *)malloc( s->cap );
		s->str[0] = '\0';
	}
	else {
		s->cap = 0;
		s->str = (char *)&s->len;
	}
	return (dgn_str_t)s;
}

dgn_str_t dgn_str_init_dup( const dgn_str_t str )
{
	if( str == NULL )
		return dgn_str_init( 0 );
	struct dgn_str_st * me = dgn_str_init( str->cap );
	if( str->len > 0 )
		memcpy( me->str, str->str, str->len + 1 );
	return me;
}

dgn_str_t dgn_str_init_c( const char * str, int len ) // len == -1 means total
{
	if( str == NULL || len == 0 )
		return dgn_str_init( 0 );
	if( len < 0 )
		len = strlen( str );
	struct dgn_str_st * me = dgn_str_init( len + 1 );
	memcpy( me->str, str, len );
	me->str[len] = '\0';
	me->len = len;
	return me;
}

int dgn_str_reserve( dgn_str_t me, int capacity )
{
	if( me == NULL ) {
		PR_DEBUG( "me is NULL" );
		return -1;
	}
	if( me->cap == 0 && capacity <= 0 )
		return 0;
	if( me->cap < capacity ) {
		int cap = me->cap * 17 / 8; // 2.125
		if( cap < capacity )
			cap = capacity;
		char * p = (char *)malloc( cap );
		if( me->len > 0 )
			memcpy( p, me->str, me->len );
		p[me->len] = '\0';
		if( me->cap != 0 )
			free( me->str );
		me->str = p;
		me->cap = cap;
	}
	return me->cap;
}

int dgn_str_len( const dgn_str_t me )
{
	if( me == NULL ) {
		PR_DEBUG( "me is NULL" );
		return 0;
	}
	return ((struct dgn_str_st *)me)->len;
}

const char * dgn_str_c( const dgn_str_t me ) // never return NULL
{
	if( me == NULL ) {
		PR_DEBUG( "me is NULL" );
		return "";
	}
	return ((struct dgn_str_st *)me)->str;
}

// auto enlarge
const char * dgn_str_assign( dgn_str_t me, const dgn_str_t str )
{
	if( me == NULL ) {
		PR_DEBUG( "me is NULL" );
		return "";
	}
	if( str == NULL )
		return dgn_str_assign_c( me, NULL, 0 );
	if( str->cap == 0 ) {
		if( me->cap == 0 )
			return me->str;
		me->cap = 0;
		me->len = 0;
		free( me->str );
		me->str = (char *)&me->len;
		return me->str;
	}

	me->len = 0;
	dgn_str_reserve( me, str->len + 1 );
	memcpy( me->str, str->str, str->len + 1 );
	me->len = str->len;
	return me->str;
}

const char * dgn_str_assign_c( dgn_str_t me, const char * str, int len )
{
	if( me == NULL ) {
		PR_DEBUG( "me is NULL" );
		return "";
	}
	if( str == NULL ) {
		if( me->cap == 0 )
			return me->str;
		me->cap = 0;
		me->len = 0;
		free( me->str );
		me->str = (char *)&me->len;
		return me->str;
	}

	if( len < 0 )
		len = strlen(str);
	if( len == 0 && me->cap == 0 )
		return me->str;

	me->len = 0;
	dgn_str_reserve( me, len + 1 );
	if( len > 0 )
		memcpy( me->str, str, len );
	me->str[len] = '\0';
	me->len = len;
	return me->str;
}

const char * dgn_str_append( dgn_str_t me, const dgn_str_t str )
{
	if( me == NULL ) {
		PR_DEBUG( "me is NULL" );
		return "";
	}
	if( str == NULL || str->len == 0 )
		return me->str;
	dgn_str_reserve( me, me->len + str->len + 1 );
	memcpy( me->str + me->len, str->str, str->len + 1 );
	me->len += str->len;
	return me->str;
}

const char * dgn_str_append_c( dgn_str_t me, const char * str, int len )
{
	if( me == NULL ) {
		PR_DEBUG( "me is NULL" );
		return "";
	}
	if( str == NULL )
		return me->str;
	if( len < 0 )
		len = strlen(str);
	if( len == 0 )
		return me->str;
	dgn_str_reserve( me, me->len + len + 1 );
	memcpy( me->str + me->len, str, len );
	me->str[me->len + len] = '\0';
	me->len += len;
	return me->str;
}

const char * dgn_str_assign_fmt( dgn_str_t me, const char * fmt, ... )
{
	if( me == NULL ) {
		PR_DEBUG( "me is NULL" );
		return "";
	}
	if( fmt == NULL )
		return dgn_str_assign_c( me, NULL, 0 );

	int len = -1;
	if( me->cap > 0 ) {
		va_list ap;
		va_start( ap, fmt );
		len = vsnprintf( me->str, me->cap, fmt, ap );
		va_end( ap );
	}
	if( len < 0 ) {
		va_list ap;
		va_start( ap, fmt );
#ifdef _WIN32
		len = _vscprintf( fmt, ap );
#else
		len = vsnprintf( NULL, 0, fmt, ap );
#endif
		va_end( ap );
	}
	if( len >= me->cap ) {
		me->len = 0;
		dgn_str_reserve( me, len + 1 );
		va_list ap;
		va_start( ap, fmt );
		len = vsnprintf( me->str, me->cap, fmt, ap );
		va_end( ap );
	}
	if( len >= 0 )
		me->len = len;
	else
		me->len = 0;
	me->str[me->len] = '\0';
	return me->str;
}

const char * dgn_str_append_fmt( dgn_str_t me, const char * fmt, ... )
{
	if( me == NULL ) {
		PR_DEBUG( "me is NULL" );
		return "";
	}
	if( fmt == NULL )
		return me->str;

	int len = -1;
	if( me->cap > 0 ) {
		va_list ap;
		va_start( ap, fmt );
		len = vsnprintf( me->str + me->len, me->cap - me->len, fmt, ap );
		va_end( ap );
	}
	if( len < 0 ) {
		va_list ap;
		va_start( ap, fmt );
#ifdef _WIN32
		len = _vscprintf( fmt, ap );
#else
		len = vsnprintf( NULL, 0, fmt, ap );
#endif
		va_end( ap );
	}
	if( me->len + len >= me->cap ) {
		dgn_str_reserve( me, me->len + len + 1 );
		va_list ap;
		va_start( ap, fmt );
		len = vsnprintf( me->str + me->len, me->cap - me->len, fmt, ap );
		va_end( ap );
	}
	if( len >= 0 )
		me->len += len;
	me->str[me->len] = '\0';
	return me->str;
}

int dgn_str_cmp( const dgn_str_t me, const dgn_str_t str )
{
	if( me == NULL ) {
		if( str == NULL )
			return 0;
		return -1;
	}
	else if( str == NULL )
		return 1;
	return strcmp( me->str, str->str );
}

int dgn_str_cmp_c( const dgn_str_t me, const char * str, int len )
{
	if( me == NULL ) {
		if( str == NULL )
			return 0;
		return -1;
	}
	else if( str == NULL )
		return 1;
	if( len < 0 )
		return strcmp( me->str, str );
	return strncmp( me->str, str, len );
}

int dgn_str_casecmp( const dgn_str_t me, const dgn_str_t str )
{
	if( me == NULL ) {
		if( str == NULL )
			return 0;
		return -1;
	}
	else if( str == NULL )
		return 1;
#ifdef _WIN32
	return _stricmp( me->str, str->str );
#else
	return strcasecmp( me->str, str->str );
#endif
}

int dgn_str_casecmp_c( const dgn_str_t me, const char * str, int len )
{
	if( me == NULL ) {
		if( str == NULL )
			return 0;
		return -1;
	}
	else if( str == NULL )
		return 1;
#ifdef _WIN32
	if( len < 0 )
		return _stricmp( me->str, str );
	return _strnicmp( me->str, str, len );
#else
	if( len < 0 )
		return strcasecmp( me->str, str );
	return strncasecmp( me->str, str, len );
#endif
}

void dgn_str_fini( dgn_str_t me )
{
	if( me == NULL )
		return;
	if( me->cap != 0 )
		free( me->str );
	free( me );
	return;
}

